package org.jeecg.activiti.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.activiti.bpmn.model.*;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.apache.commons.lang.StringUtils;
import org.jeecg.activiti.entity.ActivitiConstant;
import org.jeecg.common.api.vo.Result;
import org.springframework.util.CollectionUtils;

/**
 * <p>
 * Activiti工作流工具类
 * </p>
 * 
 * @author FRH
 * @time 2018年12月10日上午11:26:02
 * @version 1.0
 */
public class ActivitiUtils {

	/**
	 * <p>
	 * 获取流程走过的线
	 * </p>
	 * 
	 * @param bpmnModel
	 *            流程对象模型
	 * @param processDefinitionEntity
	 *            流程定义对象
	 * @param historicActivityInstances
	 *            历史流程已经执行的节点，并已经按执行的先后顺序排序
	 * @return List<String> 流程走过的线
	 * @author FRH
	 * @time 2018年12月10日上午11:26:19
	 * @version 1.0
	 */
	public static List<String> getHighLightedFlows(BpmnModel bpmnModel, ProcessDefinitionEntity processDefinitionEntity,
			List<HistoricActivityInstance> historicActivityInstances) {
		// 用以保存高亮的线flowId
		List<String> highFlows = new ArrayList<String>();
		if (historicActivityInstances == null || historicActivityInstances.size() == 0)
			return highFlows;

		// 遍历历史节点
		for (int i = 0; i < historicActivityInstances.size() - 1; i++) {
			// 取出已执行的节点
			HistoricActivityInstance activityImpl_ = historicActivityInstances.get(i);

			// 用以保存后续开始时间相同的节点
			List<FlowNode> sameStartTimeNodes = new ArrayList<FlowNode>();

			// 获取下一个节点（用于连线）
			FlowNode sameActivityImpl = getNextFlowNode(bpmnModel, historicActivityInstances, i, activityImpl_);
			// FlowNode sameActivityImpl = (FlowNode)
			// bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(i
			// + 1).getActivityId());

			// 将后面第一个节点放在时间相同节点的集合里
			if (sameActivityImpl != null)
				sameStartTimeNodes.add(sameActivityImpl);

			// 循环后面节点，看是否有与此后继节点开始时间相同的节点，有则添加到后继节点集合
			for (int j = i + 1; j < historicActivityInstances.size() - 1; j++) {
				HistoricActivityInstance activityImpl1 = historicActivityInstances.get(j);// 后续第一个节点
				HistoricActivityInstance activityImpl2 = historicActivityInstances.get(j + 1);// 后续第二个节点
				if (activityImpl1.getStartTime().getTime() != activityImpl2.getStartTime().getTime())
					break;

				// 如果第一个节点和第二个节点开始时间相同保存
				FlowNode sameActivityImpl2 = (FlowNode) bpmnModel.getMainProcess()
						.getFlowElement(activityImpl2.getActivityId());
				sameStartTimeNodes.add(sameActivityImpl2);
			}

			// 得到节点定义的详细信息
			FlowNode activityImpl = (FlowNode) bpmnModel.getMainProcess()
					.getFlowElement(historicActivityInstances.get(i).getActivityId());
			// 取出节点的所有出去的线，对所有的线进行遍历
			List<SequenceFlow> pvmTransitions = activityImpl.getOutgoingFlows();
			for (SequenceFlow pvmTransition : pvmTransitions) {
				// 获取节点
				FlowNode pvmActivityImpl = (FlowNode) bpmnModel.getMainProcess()
						.getFlowElement(pvmTransition.getTargetRef());

				// 不是后继节点
				if (!sameStartTimeNodes.contains(pvmActivityImpl))
					continue;

				// 如果取出的线的目标节点存在时间相同的节点里，保存该线的id，进行高亮显示
				highFlows.add(pvmTransition.getId());
			}
		}

		// 返回高亮的线
		return highFlows;
	}

	/**
	 * <p>
	 * 获取下一个节点信息
	 * </p>
	 * 
	 * @param bpmnModel
	 *            流程模型
	 * @param historicActivityInstances
	 *            历史节点
	 * @param i
	 *            当前已经遍历到的历史节点索引（找下一个节点从此节点后）
	 * @param activityImpl_
	 *            当前遍历到的历史节点实例
	 * @return FlowNode 下一个节点信息
	 * @author FRH
	 * @time 2018年12月10日上午11:26:55
	 * @version 1.0
	 */
	private static FlowNode getNextFlowNode(BpmnModel bpmnModel,
			List<HistoricActivityInstance> historicActivityInstances, int i, HistoricActivityInstance activityImpl_) {
		// 保存后一个节点
		FlowNode sameActivityImpl = null;

		// 如果当前节点不是用户任务节点，则取排序的下一个节点为后续节点
		if (!"userTask".equals(activityImpl_.getActivityType())) {
			// 是最后一个节点，没有下一个节点
			if (i == historicActivityInstances.size())
				return sameActivityImpl;
			// 不是最后一个节点，取下一个节点为后继节点
			sameActivityImpl = (FlowNode) bpmnModel.getMainProcess()
					.getFlowElement(historicActivityInstances.get(i + 1).getActivityId());// 找到紧跟在后面的一个节点
			// 返回
			return sameActivityImpl;
		}

		// 遍历后续节点，获取当前节点后续节点
		for (int k = i + 1; k <= historicActivityInstances.size() - 1; k++) {
			// 后续节点
			HistoricActivityInstance activityImp2_ = historicActivityInstances.get(k);
			// 都是userTask，且主节点与后续节点的开始时间相同，说明不是真实的后继节点
			if ("userTask".equals(activityImp2_.getActivityType())
					&& activityImpl_.getStartTime().getTime() == activityImp2_.getStartTime().getTime())
				continue;
			// 找到紧跟在后面的一个节点
			sameActivityImpl = (FlowNode) bpmnModel.getMainProcess()
					.getFlowElement(historicActivityInstances.get(k).getActivityId());
			break;
		}
		return sameActivityImpl;
	}

	/**
	 * 流程校验，因流程判断比较复杂，取巧借用事务回滚机制，如果校验失败，则回滚
	 * @param bpmnModel
	 */
	public static Result<Object> validateFlowModel(BpmnModel bpmnModel) {

		if(bpmnModel != null){

			// 流程办理人
			Map<String, List<String>> handlerPersonMap = new HashMap<>();

			Collection<FlowElement> flowElements = bpmnModel.getMainProcess().getFlowElements();
			List<StartEvent> startEvents = new ArrayList<>();
			List<UserTask> userTasks = new ArrayList<>();
			List<EndEvent> endEvents = new ArrayList<>();
			List<SequenceFlow> sequenceFlows = new ArrayList<>();
			StringBuilder nodeError = new StringBuilder();
			boolean isFoundNodeNameNotSet = false;
			for (FlowElement e : flowElements) {
				if (e instanceof StartEvent) {
					StartEvent startEvent = (StartEvent)e;
					startEvents.add(startEvent);
				} else if (e instanceof SequenceFlow) {
					SequenceFlow sequenceFlow = (SequenceFlow)e;
					sequenceFlows.add(sequenceFlow);
				} else if (e instanceof UserTask) {
					UserTask userTask = (UserTask) e;
					userTasks.add(userTask);
					if(StringUtils.isBlank(userTask.getName())){
						isFoundNodeNameNotSet = true;
					}

					List<String> assigens = new ArrayList<>();
					if (userTask.getAssignee() != null) {
						assigens.add(userTask.getAssignee());
					}
					assigens.addAll(userTask.getCandidateUsers());
					assigens.addAll(userTask.getCandidateGroups());
					if (assigens.size() != 0) {
						handlerPersonMap.put(userTask.getId(), assigens);
					}

				} else if (e instanceof EndEvent) {
					EndEvent endEvent = (EndEvent) e;
					endEvents.add(endEvent);
				}
			}

			if(CollectionUtils.isEmpty(startEvents)){
				nodeError.append(ActivitiConstant.PRCOESS_STARTEVENT_NOT_FOUND).append(ActivitiConstant.SEMICOLON).append(ActivitiConstant.HTML_NEWLINE);
			}

			if(CollectionUtils.isEmpty(userTasks)){
				nodeError.append(ActivitiConstant.PRCOESS_USERTASK_NOT_FOUND).append(ActivitiConstant.SEMICOLON).append(ActivitiConstant.HTML_NEWLINE);
			}

			if (CollectionUtils.isEmpty(endEvents)){
				nodeError.append(ActivitiConstant.PRCOESS_ENDEVENT_NOT_FOUND).append(ActivitiConstant.SEMICOLON).append(ActivitiConstant.HTML_NEWLINE);
			}

			if(StringUtils.isNotBlank(nodeError.toString())){
				return Result.error(nodeError.toString());
			}

			if(isFoundNodeNameNotSet){
				return Result.error(ActivitiConstant.PRCOESS_NODE_NAME_NOT_SET);
			}


			if(!CollectionUtils.isEmpty(sequenceFlows)){
				for(SequenceFlow sequenceFlow : sequenceFlows){
					if(StringUtils.isBlank(sequenceFlow.getSourceRef()) && StringUtils.isBlank(sequenceFlow.getTargetRef())){
						nodeError.append(ActivitiConstant.PRCOESS_SEQUENCEFLOW_RELATE_NODE_NOT_FOUND).append(ActivitiConstant.SEMICOLON).append(ActivitiConstant.HTML_NEWLINE);
						break;
					}
				}
			}


			StringBuilder handlerPersonNotFound = new StringBuilder();

			for(StartEvent startEvent : startEvents){
				List<SequenceFlow> outGoingFlows = startEvent.getOutgoingFlows();
				String nodeName = StringUtils.isBlank(startEvent.getName()) ? ActivitiConstant.ACT_TYPE_START_CN : startEvent.getName();
				if(CollectionUtils.isEmpty(outGoingFlows)){
					nodeError.append("【").append(nodeName).append("】").append(ActivitiConstant.PRCOESS_OUTGOING_FLOWS_NOT_FOUND).append(ActivitiConstant.SEMICOLON).append(ActivitiConstant.HTML_NEWLINE);
				}
				//节点之间连线校验
				validateSequenceFlows(nodeError, nodeName, outGoingFlows, ActivitiConstant.SEQUENCE_FLOW_TYPE_TARGET);

				// 判断开始任务办理人
				/*List<String> handlerPersonList = handlerPersonMap.get(startEvent.getId());
				if(CollectionUtils.isEmpty(handlerPersonList)){
					handlerPersonNotFound.append("[").append(startEvent.getName()).append("]").append(ActivitiConstant.PRCOESS_HANDLER_PERSON_NOT_FOUND).append(ActivitiConstant.SEMICOLON).append(ActivitiConstant.HTML_NEWLINE);
				}*/

			}

			for(UserTask userTask : userTasks){
				List<SequenceFlow> incomingFlows = userTask.getIncomingFlows();
				if(CollectionUtils.isEmpty(incomingFlows)){
					nodeError.append("【").append(StringUtils.isBlank(userTask.getName())? ActivitiConstant.PRCOESS_WIHTOUT_NAME : userTask.getName()).append("】").append(ActivitiConstant.PRCOESS_INCOMING_FLOWS_NOT_FOUND).append(ActivitiConstant.SEMICOLON).append(ActivitiConstant.HTML_NEWLINE);
				}
				//节点之间连线校验
				validateSequenceFlows(nodeError,StringUtils.isBlank(userTask.getName())? ActivitiConstant.PRCOESS_WIHTOUT_NAME : userTask.getName(),incomingFlows,ActivitiConstant.SEQUENCE_FLOW_TYPE_SOURCE);

				List<SequenceFlow> outGoingFlows = userTask.getOutgoingFlows();
				if(CollectionUtils.isEmpty(outGoingFlows)){
					nodeError.append("【").append(StringUtils.isBlank(userTask.getName())? ActivitiConstant.PRCOESS_WIHTOUT_NAME : userTask.getName()).append("】").append(ActivitiConstant.PRCOESS_OUTGOING_FLOWS_NOT_FOUND).append(ActivitiConstant.SEMICOLON).append(ActivitiConstant.HTML_NEWLINE);
				}
				//节点之间连线校验
				validateSequenceFlows(nodeError, StringUtils.isBlank(userTask.getName())? ActivitiConstant.PRCOESS_WIHTOUT_NAME : userTask.getName(), outGoingFlows,ActivitiConstant.SEQUENCE_FLOW_TYPE_TARGET);

				List<String> handlerPersonList = handlerPersonMap.get(userTask.getId());
				if(CollectionUtils.isEmpty(handlerPersonList)){
					handlerPersonNotFound.append("【").append(userTask.getName()).append("】").append(ActivitiConstant.PRCOESS_HANDLER_PERSON_NOT_FOUND).append(ActivitiConstant.SEMICOLON).append(ActivitiConstant.HTML_NEWLINE);
				}

			}

			for(EndEvent endEvent : endEvents){
				List<SequenceFlow> incomingFlows = endEvent.getIncomingFlows();
				String nodeName = StringUtils.isBlank(endEvent.getName()) ? ActivitiConstant.ACT_TYPE_END_CN : endEvent.getName();
				if(CollectionUtils.isEmpty(incomingFlows)){
					nodeError.append("【").append(nodeName).append("】").append(ActivitiConstant.PRCOESS_INCOMING_FLOWS_NOT_FOUND).append(ActivitiConstant.SEMICOLON).append(ActivitiConstant.HTML_NEWLINE);
				}
				//节点之间连线校验
				validateSequenceFlows(nodeError,nodeName,incomingFlows,ActivitiConstant.SEQUENCE_FLOW_TYPE_SOURCE);
			}

			if(StringUtils.isNotBlank(handlerPersonNotFound.toString())){
				nodeError.append(handlerPersonNotFound.toString());
			}

			if(StringUtils.isNotBlank(nodeError.toString())){
				return Result.error(nodeError.toString());
			}

			return Result.OK();
		} else {
			return Result.error("流程为空");
		}
	}

	/**
	 * 节点之间连接线校验
	 * @param nodeError
	 * @param nodeName
	 * @param sequenceFlows
	 */
	private static void validateSequenceFlows(StringBuilder nodeError, String nodeName, List<SequenceFlow> sequenceFlows,String sequenceFlowType) {
		if(CollectionUtils.isEmpty(sequenceFlows)){
			return;
		}
		String tip = null;
		String seqRef = null;
		int  conditionCount = sequenceFlows.size();
		for(SequenceFlow sequenceFlow : sequenceFlows){
			switch (sequenceFlowType){
				case "SOURCE":
					seqRef = sequenceFlow.getSourceRef();
					tip = ActivitiConstant.PRCOESS_INCOMING_FLOWS_NOT_FOUND;
					break;
				case "TARGET":
					seqRef = sequenceFlow.getTargetRef();
					tip = ActivitiConstant.PRCOESS_OUTGOING_FLOWS_NOT_FOUND;
					break;
				default:
					break;
			}

			if(StringUtils.isBlank(seqRef)){
				nodeError.append("【").append(nodeName).append("】").append(tip).append(ActivitiConstant.SEMICOLON).append(ActivitiConstant.HTML_NEWLINE);
				break;
			}

			//存在多个分支时，则进行出口条件校验
			if(sequenceFlows.size() > 1){
				String conditionExpression = sequenceFlow.getConditionExpression();
				if(StringUtils.isBlank(conditionExpression)){
					conditionCount = conditionCount - 1;
				}

			}

		}

		if(conditionCount == 0){
			nodeError.append("【").append(nodeName).append("】").append(ActivitiConstant.PRCOESS_EXPORT_RULE_NOT_FOUND).append(ActivitiConstant.SEMICOLON).append(ActivitiConstant.HTML_NEWLINE);
		}
	}
}
